/*
 * XMPPServerHelper.java
 *
 * Created on March 23, 2007, 1:36 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package org.xeerkat.restlet.xmpp;


import org.restlet.Server;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Reader;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.infoset.xml.Characters;
import org.infoset.xml.DestinationSource;
import org.infoset.xml.DocumentLoader;
import org.infoset.xml.Element;
import org.infoset.xml.ElementEnd;
import org.infoset.xml.Infoset;
import org.infoset.xml.InfosetFactory;
import org.infoset.xml.Item;
import org.infoset.xml.ItemConstructor;
import org.infoset.xml.ItemDestination;
import org.infoset.xml.Name;
import org.infoset.xml.XMLException;
import org.infoset.xml.sax.SAXDocumentLoader;
import org.infoset.xml.util.DocumentDestination;
import org.infoset.xml.util.DocumentSource;
import org.infoset.xml.util.HandlerEngine;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.data.ChallengeResponse;
import org.restlet.data.ChallengeScheme;
import org.restlet.data.Cookie;
import org.restlet.data.Form;
import org.restlet.data.MediaType;
import org.restlet.data.Method;
import org.restlet.data.Reference;
import org.restlet.data.Status;
import org.restlet.engine.http.HttpServerHelper;
import org.restlet.representation.OutputRepresentation;
import org.restlet.representation.Representation;
import org.restlet.representation.StringRepresentation;
import org.xeerkat.restlet.xmpp.util.Base64Decoder;
import org.xeerkat.xmpp.ID;
import org.xeerkat.xmpp.StanzaDestination;

/**
 *
 * @author alex
 */
public class XMPPServerHelper extends HttpServerHelper {

   static final ThreadGroup threadGroup = new ThreadGroup("xmpp.restlet.server");
   static long RECEIVE_TIMEOUT = 2*60*1000; // 2 minutes in milliseconds
   
   static class ChunkedRepresentation extends OutputRepresentation {
      ByteArrayOutputStream buffer = null;
      OutputStream os = null;
      boolean finished = false;
      ChunkedRepresentation(MediaType type) 
      {
         super(type);
      }
      public void write(OutputStream os) 
         throws IOException
      {
         synchronized (this) {
            this.os = os;
            if (buffer!=null) {
               buffer.writeTo(os);
               buffer = null;
            }
         }
         if (!finished) {
            synchronized (this) {
               try {
                  wait(RECEIVE_TIMEOUT);
               } catch (InterruptedException ex) {
                  throw new IOException("Interrupted while waiting for data chunk.");
               }
            }
         }
      }
      
      public void receiveChunk(byte [] chunk,int start, int length) 
         throws IOException
      {
         if (os!=null) {
            os.write(chunk,start,length);
         } else if (buffer==null) {
            synchronized (this) {
               if (buffer!=null) {
                  receiveChunk(chunk,start,length);
                  return;
               }
               buffer = new ByteArrayOutputStream();
               buffer.write(chunk,start,length);
            }
         } else {
            buffer.write(chunk,start,length);
         }
      }
      public void finished() {
         this.finished = true;
         synchronized (this) {
            notify();
         }
      }
   }
   static Logger LOG = Logger.getLogger(XMPPServerHelper.class.getName());

   class StanzaHandler implements HandlerEngine.Handler {
      int level;
      HandlerEngine.Handler parent;
      HandlerEngine engine;
      public void init(HandlerEngine engine) {
         this.engine = engine;
         this.level = engine.getLevel();
         this.parent = engine.getCurrentHandler();
      }
      
      public int getStartLevel() {
         return level;
      }
      public HandlerEngine.Handler pop() {
         return parent;
      }
      public void send(Item item)
         throws XMLException
      {
         if (engine.getLevel()==1 && item.getType()==Item.ItemType.ElementItem) {
            Element e = (Element)item;
            Name name = e.getName();
            if (name.equals(org.xeerkat.xmpp.Protocol.MESSAGE)) {
               if (LOG.isLoggable(Level.FINE)) {
                  LOG.fine("Received message over "+identity.getId());
               }
               HandlerEngine.Handler msg = new MessageHandler();
               engine.push(msg,item);
            } else {
               if (name.equals(org.xeerkat.xmpp.Protocol.PRESENCE)) {
                  String type = e.getAttributeValue("type");
                  if (type==null) {
                     identity.notifyOnline(new ID(e.getAttributeValue("from")));
                  } else if ("unavailable".equals(type)) {
                     identity.notifyOffline(new ID(e.getAttributeValue("from")));
                  } else if ("probe".equals(type)) {
                     try {
                        String from = e.getAttributeValue("from");
                        ID fromId = from==null ? null : new ID(from);
                        identity.sendOnline(fromId);
                     } catch (XMPPException ex) {
                     }
                  }
               }
               final String theSender = e.getAttributeValue("from");
               HandlerEngine.Handler stanzaHandler = new HandlerEngine.Handler() {
                  StanzaDestination stanza = new StanzaDestination();
                  int startLevel;
                  HandlerEngine.Handler parent;
                  public void init(HandlerEngine engine) {
                     this.startLevel = engine.getLevel();
                     this.parent = engine.getCurrentHandler();
                  }
                  public int getStartLevel() {
                     return startLevel;
                  }
                  public HandlerEngine.Handler pop() {
                     return StanzaHandler.this;
                  }
                  
                  public void send(Item item)
                     throws XMLException
                  {
                     stanza.send(item);
                     if (engine.getLevel()==startLevel && item.getType()==Item.ItemType.ElementEndItem) {
                        Reference targetRef = new Reference("riap://component/xmpp/"+identity.getConnectionId());
                        Request request = new Request(Method.POST,targetRef);
                        request.getAttributes().put("org.xeerkat.xmpp.id",identity.getConnectionId().toString());
                        if (theSender!=null) {
                           request.getAttributes().put("org.xeerkat.xmpp.from.id",theSender);
                        }
                        request.getAttributes().put("org.xeerkat.xmpp.type",stanza.getStanza().getName());
                        request.getAttributes().put("org.xeerkat.xmpp.stanza",stanza.getStanza());
                        Form headers = new Form();


                        request.setHostRef(new Reference("xmpp://"+identity.getHost()));
                        request.getAttributes().put("org.restlet.http.headers",headers);
                        headers.add("x-xmpp-sender",theSender);
                        request.setEntity(new StanzaRepresentation(stanza.getStanza()));
                        request.setResourceRef(targetRef);

                        if (getContext().getLogger().isLoggable(Level.FINE)) {
                           getContext().getLogger().fine("Routing XMPP stanza "+stanza.getStanza().getName()+" to "+targetRef+" from "+theSender);
                        }
                        request.getClientInfo().setAddress(theSender);

                        if (!XMPPServerHelper.this.running) {
                           getLogger().info("Cannot deliever stanza as server is stopped: "+request.getEntityAsText());
                           return;
                        }
                        Response response = getHelped().getContext().getClientDispatcher().handle(request);

                        if (response.isEntityAvailable()) {
                           Representation entity = response.getEntity();
                           MediaType mediaType = entity.getMediaType();
                           if (entity  instanceof StanzaRepresentation) {
                              Element responseStanza = ((StanzaRepresentation)entity).getStanza();
                              DocumentSource.generate(responseStanza, false, identity.connection.getStanzaDestination());
                           } else if (MediaType.APPLICATION_XML.equals(mediaType) || MediaType.TEXT_XML.equals(mediaType) || MediaType.APPLICATION_ALL_XML.includes(mediaType)) {
                              try {
                                 Reader reader = entity.getReader();
                                 loader.generate(reader, identity.connection.getStanzaDestination());
                                 reader.close();
                              } catch (Exception ex) {
                                 response.setStatus(Status.CONNECTOR_ERROR_CONNECTION);
                                 response.setEntity(new StringRepresentation("Error while sending stanza: " + ex.getMessage(), MediaType.TEXT_PLAIN));
                                 return;
                              }
                           } else {
                              getContext().getLogger().severe("Unable to process representation class: "+response.getEntity().getClass().getName());
                           }
                        }

                     }
                  }
               };
               engine.push(stanzaHandler,item);
            }
         }
      }
   }
   
   class MessageHandler implements HandlerEngine.Handler {
      int startLevel;
      HandlerEngine.Handler parent;
      HandlerEngine engine;
      StanzaDestination stanza = new StanzaDestination();
      DecoderHandler messageDecoder;
      boolean decoded;
      public void init(HandlerEngine engine) {
         this.engine = engine;
         this.startLevel = engine.getLevel();
         this.parent = engine.getCurrentHandler();
         this.decoded = false;
      }
      
      public int getStartLevel() {
         return startLevel;
      }
      public HandlerEngine.Handler pop() {
         return parent;
      }
      public void send(Item item)
         throws XMLException
      {
         if (LOG.isLoggable(Level.FINE)) {
            LOG.info(startLevel+" "+engine.getLevel()+" "+item);
         }
         if (engine.getLevel()==startLevel) {
            stanza.send(item);
            switch (item.getType()) {
               case ElementItem:
                  messageDecoder = new DecoderHandler(((Element)item).getAttributeValue("from"));
               break;
               case ElementEndItem:
                  Element stanzaE = stanza.getStanza();
                  if (stanzaE.size()>0 || !decoded) {
                     // we have a stanza to send
                     String sender = stanzaE.getAttributeValue("from");
                     Reference targetRef = new Reference("riap://component/xmpp/"+identity.getConnectionId());
                     Request request = new Request(Method.POST,targetRef);
                     request.getAttributes().put("org.xeerkat.xmpp.id",identity.getConnectionId().toString());
                     if (sender!=null) {
                        request.getAttributes().put("org.xeerkat.xmpp.from.id",sender);
                     }
                     request.getAttributes().put("org.xeerkat.xmpp.type",stanzaE.getName());
                     request.getAttributes().put("org.xeerkat.xmpp.stanza",stanzaE);
                     Form headers = new Form();


                     request.setHostRef("xmpp://"+identity.getHost());
                     request.getAttributes().put("org.restlet.http.headers",headers);
                     headers.add("x-xmpp-sender",sender);
                     request.setEntity(new StanzaRepresentation(stanza.getStanza()));
                     request.setResourceRef(targetRef);

                     if (getContext().getLogger().isLoggable(Level.FINE)) {
                        getContext().getLogger().fine("Routing XMPP stanza "+stanza.getStanza().getName()+" to "+targetRef+" from "+sender);
                     }
                     request.getClientInfo().setAddress(sender);

                     if (!XMPPServerHelper.this.running) {
                        getLogger().info("Cannot deliever stanza as server is stopped: "+request.getEntityAsText());
                        return;
                     }
                     Response response = getHelped().getContext().getClientDispatcher().handle(request);

                     if (response.isEntityAvailable()) {
                        Representation entity = response.getEntity();
                        MediaType mediaType = entity.getMediaType();
                        if (entity instanceof StanzaRepresentation) {
                           Element responseStanza = ((StanzaRepresentation)entity).getStanza();
                           DocumentSource.generate(responseStanza, false, identity.connection.getStanzaDestination());
                        } else if (MediaType.APPLICATION_XML.equals(mediaType) || MediaType.TEXT_XML.equals(mediaType) || MediaType.APPLICATION_ALL_XML.includes(mediaType)) {
                           try {
                              Reader reader = entity.getReader();
                              loader.generate(reader, identity.connection.getStanzaDestination());
                              reader.close();
                           } catch (Exception ex) {
                              response.setStatus(Status.CONNECTOR_ERROR_CONNECTION);
                              response.setEntity(new StringRepresentation("Error while sending stanza: " + ex.getMessage(), MediaType.TEXT_PLAIN));
                              return;
                           }
                        } else {
                           getContext().getLogger().severe("Unable to process representation class: "+response.getEntity().getClass().getName());
                        }
                     }
                  }
            }
         } else if (engine.getLevel()==(startLevel+1)) {
            switch (item.getType()) {
               case ElementItem:
                  Name name = ((Element)item).getName();
                  if (name.equals(MessageSequence.REQUEST) ||
                      name.equals(MessageSequence.CHUNK) ||
                      name.equals(MessageSequence.RESPONSE) ||
                      name.equals(MessageSequence.END) ||
                      name.equals(MessageSequence.MANAGE)) {
                      decoded = true;
                      engine.push(messageDecoder, item);
                  } else {
                     stanza.send(item);
                  }
                  break;
               default:
                  stanza.send(item);
            }
         } else {
            stanza.send(item);
         }
      }
   }
   class ErrorHandler implements HandlerEngine.Handler {
      int startLevel;
      HandlerEngine.Handler parent;
      HandlerEngine engine;
      ID id;
      public void init(HandlerEngine engine) {
         this.engine = engine;
         this.startLevel = engine.getLevel();
         this.parent = engine.getCurrentHandler();
      }
      
      public int getStartLevel() {
         return startLevel;
      }
      public HandlerEngine.Handler pop() {
         return parent;
      }
      public void send(Item item)
         throws XMLException
      {
         switch (item.getType()) {
            case ElementItem:
               Element e = (Element)item;
               Name name = e.getName();
               if (name.equals(org.xeerkat.xmpp.Protocol.MESSAGE)) {
                  /*
                  for (Name aname : e.getAttributes().keySet()) {
                     LOG.info("Message: "+aname+"="+e.getAttributeValue(aname));
                  }
                   */
                  String from = e.getAttributeValue("from");
                  id = from==null ? null : new ID(from) ;
               } else if (name.equals(org.xeerkat.xmpp.Protocol.ERROR)) {
                  /*
                  for (Name aname : e.getAttributes().keySet()) {
                     LOG.info("Error: "+aname+"="+e.getAttributeValue(aname));
                  }
                   */
                  if ("cancel".equals(e.getAttributeValue("type")) && id!=null) {
                     identity.notifyOffline(id);
                  }
               }
         }
      }
   }
   
   class ManageHandler implements HandlerEngine.Handler {
      int startLevel;
      HandlerEngine.Handler parent;
      HandlerEngine engine;
      DocumentDestination docDest;
      ID theSender;
      String username;
      String password;
      String authMethod;
      ManageHandler(ID theSender) {
         this.theSender = theSender;
         this.username = null;
         this.password = null;
         this.authMethod = null;
      }
      public void init(HandlerEngine engine) {
         this.engine = engine;
         this.startLevel = engine.getLevel();
         this.parent = engine.getCurrentHandler();
         this.docDest = new DocumentDestination();
      }
      
      public int getStartLevel() {
         return startLevel;
      }
      public HandlerEngine.Handler pop() {
         return parent;
      }
      public void send(Item item)
         throws XMLException
      {
         if (engine.getLevel()==startLevel) {
            switch (item.getType()) {
               case ElementItem:
                  Element e = (Element)item;
                  username =  e.getAttributeValue("username");
                  password = e.getAttributeValue("password");
                  authMethod = e.getAttributeValue("method");
                  break;
               case ElementEndItem:
                  identity.notifyManagement(theSender,authMethod,username,password,docDest.getDocument());
            }
         } else if (engine.getLevel()>=(startLevel+1)) {
            switch (item.getType()) {
               case ElementItem:
               case ElementEndItem:
                  docDest.send(item);
                  break;
               default:
                  if (engine.getLevel()>(startLevel+1)) {
                     docDest.send(item);
                  }
            }
         }
      }
   }
   
   class BuilderHandler implements HandlerEngine.Handler {
      int startLevel;
      HandlerEngine.Handler parent;
      HandlerEngine engine;
      StringBuilder builder;
      BuilderHandler(StringBuilder builder) {
         this.builder = builder;
      }
      public void init(HandlerEngine engine) {
         this.engine = engine;
         this.startLevel = engine.getLevel();
         this.parent = engine.getCurrentHandler();
      }
      
      public int getStartLevel() {
         return startLevel;
      }
      public HandlerEngine.Handler pop() {
         return parent;
      }
      public void send(Item item)
         throws XMLException
      {
         switch (item.getType()) {
            case CharactersItem:
               builder.append(((Characters)item).getText());
         }
      }
   }
   class DecoderHandler implements HandlerEngine.Handler {
      boolean inRequestResponse;
      UUID id = null;
      String method = null;
      String path = null;
      int status = 0;
      boolean isRequest;
      StringBuilder builder = null;
      String mediaType;
      ChallengeResponse challengeResponse = null;
      long size;
      boolean chunked;
      Form headers = null;
      boolean hasBody;
      String theSender;
      List<Cookie> cookies = null;
      
      int startLevel;
      HandlerEngine.Handler parent;
      HandlerEngine engine;
      
      DecoderHandler(String theSender) {
         this.theSender = theSender;
         this.inRequestResponse = false;
      }
      public void init(HandlerEngine engine) {
         this.engine = engine;
         this.startLevel = engine.getLevel();
         this.parent = engine.getCurrentHandler();
      }
      
      public int getStartLevel() {
         return startLevel;
      }
      public HandlerEngine.Handler pop() {
         return parent;
      }
      public void send(Item item)
         throws XMLException
      {
         if (engine.getLevel()==startLevel) {
            switch (item.getType()) {
               case ElementItem: {
                  Element e = (Element)item;
                  Name name = e.getName();
                  if (name.equals(MessageSequence.REQUEST) || name.equals(MessageSequence.RESPONSE)) {
                     inRequestResponse = true;
                     isRequest = name.equals(MessageSequence.REQUEST);
                     id = UUID.fromString(e.getAttributeValue("id"));
                     method = e.getAttributeValue("method");
                     path = e.getAttributeValue("path");
                     status = isRequest ? 0 : Integer.parseInt(e.getAttributeValue("status"));
                     headers = new Form();
                     hasBody = false;
                  } else if (name.equals(MessageSequence.CHUNK)) {
                     builder = new StringBuilder();
                     id = UUID.fromString(e.getAttributeValue("id"));
                     if (LOG.isLoggable(Level.FINE)) {
                        LOG.fine("Received chunk for "+id);
                     }
                     engine.push(new BuilderHandler(builder),item);
                  } else if (name.equals(MessageSequence.END)) {
                     id = UUID.fromString(e.getAttributeValue("id"));
                     if (LOG.isLoggable(Level.FINE)) {
                        LOG.fine("Finished with chunks for "+id);
                     }
                     ChunkedRepresentation rep = waitingRepresentations.get(id);
                     if (rep==null) {
                        LOG.warning("Received end for "+id+" but there is no representation available.");
                     } else {
                        rep.finished();
                        waitingRepresentations.remove(id);
                     }
                  } else if (name.equals(MessageSequence.MANAGE)) {
                     LOG.fine("Management message received.");
                     engine.push(new ManageHandler(new ID(theSender)),item);
                  }
               }
                  break;
               case ElementEndItem: {
                  ElementEnd e = (ElementEnd)item;
                  Name name = e.getName();
                  if (name.equals(MessageSequence.REQUEST) || name.equals(MessageSequence.RESPONSE)) {

                     getLogger().info("Path: "+path);
                     Request request = new Request(Method.valueOf(method),new Reference(path));
                     request.getAttributes().put("org.xeerkat.xmpp.id",identity.getConnectionId().toString());
                     request.getAttributes().put("org.xeerkat.xmpp.from.id",theSender);
                     if (isRequest) {
                        request.setChallengeResponse(challengeResponse);
                        request.setHostRef("xmpp://"+identity.getHost());
                        //getLogger().info("Host domain: "+request.getHostRef().getHostDomain());
                        request.getAttributes().put("org.restlet.http.headers",headers);
                        if (cookies!=null) {
                           for (Cookie cookie : cookies) {
                              //getLogger().info("Adding cookie "+cookie.getName()+" to request");
                              request.getCookies().add(cookie);
                           }
                        }
                        headers.add("x-xmpp-sender",theSender);
                        if (hasBody) {
                           if (chunked) {
                              ChunkedRepresentation rep = new ChunkedRepresentation(MediaType.valueOf(mediaType));
                              waitingRepresentations.put(id,rep);
                              request.setEntity(rep);
                           } else {
                              request.setEntity(new StringRepresentation(builder.toString(),MediaType.valueOf(mediaType)));
                           }
                        }

                        // This is an import bit here.  Here we set the base reference to be
                        // the identity of the receiver with the local resource name (XMPP resource).
                        // Then the remaining part is correct for routing.
                        request.setResourceRef(new Reference(request.getHostRef(),request.getHostRef()+request.getResourceRef().toString()));

                        if (!XMPPServerHelper.this.running) {
                           getLogger().info("Cannot deliever request as server is stopped.");
                           return;
                        }
                       if (getContext().getLogger().isLoggable(Level.FINE)) {
                           getContext().getLogger().fine("Routing XMPP request to "+request.getResourceRef().toString());
                        }
                        request.getClientInfo().setAddress(theSender);
                        Response response = new Response(request);
                        getHelped().handle(request,response);
                        getLogger().fine("Sending response");
                        MessageSequence sequence = new MessageSequence(id,response) {
                           public ItemDestination onMessage() 
                              throws XMPPException
                           {
                              if (!identity.isConnected()) {
                                 identity.connect();
                              }
                               final ItemDestination stanza = identity.connection.getStanzaDestination();
                               if (stanza==null) {
                                  throw new XMPPException("Disconnected during send.");
                               }

                               // Wrap the messages in a XMPP message element
                               return new ItemDestination() {
                                  ItemConstructor constructor;
                                  public void send(Item item) 
                                    throws XMLException
                                  {
                                       switch (item.getType()) {
                                          case DocumentItem:
                                             stanza.send(item);
                                             constructor = item.getInfoset().createItemConstructor();
                                             Element messageE = constructor.createElement(org.xeerkat.xmpp.Protocol.MESSAGE);
                                             messageE.setAttributeValue("from",identity.connection.getBindID().toString());
                                             messageE.setAttributeValue("to",theSender);
                                             if (getLogger().isLoggable(Level.FINE)) {
                                                getLogger().info("Sending message from "+identity.getConnectionId().toString()+" to "+theSender);
                                             }
                                             stanza.send(messageE);
                                             break;
                                          case DocumentEndItem:
                                             stanza.send(constructor.createElementEnd(org.xeerkat.xmpp.Protocol.MESSAGE));
                                             stanza.send(item);
                                             break;
                                          default:
                                             stanza.send(item);
                                       }
                                  }
                               };
                           }
                        };
                        try {
                           sequence.generate();
                        } catch (IOException ex) {
                           getContext().getLogger().log(Level.SEVERE,"Cannot generate XMPP messages from response.",ex);
                        } catch (XMPPException ex) {
                           getContext().getLogger().log(Level.SEVERE,"Cannot generate XMPP messages from response.",ex);
                        }
                     } else {
                        Response response = new Response(request);
                        response.setStatus(Status.valueOf(status));
                        response.getAttributes().put("org.restlet.http.headers",headers);
                        headers.add("x-xmpp-sender",theSender);
                        if (hasBody) {
                           if (chunked) {
                              ChunkedRepresentation rep = new ChunkedRepresentation(MediaType.valueOf(mediaType));
                              waitingRepresentations.put(id,rep);
                              response.setEntity(rep);
                           } else {
                              response.setEntity(new StringRepresentation(builder.toString(),MediaType.valueOf(mediaType)));
                           }
                        }
                        response.getRequest().setResourceRef(request.getHostRef()+"/"+theSender+response.getRequest().getResourceRef().toString());
                        identity.notifyResponse(id,response);
                     }
                     headers = null;
                     builder = null;
                  } else if (name.equals(MessageSequence.CHUNK)) {
                     ChunkedRepresentation rep = waitingRepresentations.get(id);
                     if (rep==null) {
                        LOG.warning("Received chunk for "+id+" but there is no representation available.");
                     } else {
                        if (LOG.isLoggable(Level.FINE)) {
                           LOG.fine("Decoding: "+builder.toString());
                        }
                        Base64Decoder decoder = new Base64Decoder();
                        decoder.translate(builder.toString());
                        byte [] bytes = decoder.getByteArray();
                        if (LOG.isLoggable(Level.FINE)) {
                           LOG.fine("Notifying representation of bytes of length "+bytes.length);
                        }
                        try {
                           rep.receiveChunk(bytes,0,bytes.length);
                        } catch (IOException ex) {
                           throw new XMLException("I/O error while receiving chunk.",ex);
                        }
                     }
                     builder = null;
                  }
               }
            }
            
         } else if (inRequestResponse) {
            if (engine.getLevel()==(startLevel+1)) {
               switch (item.getType()) {
                  case ElementItem:
                     Element e = (Element)item;
                     if (e.getName().equals(MessageSequence.HEADER)) {
                        String name = e.getAttributeValue("name");
                        String value = e.getAttributeValue("value");
                        if (headers==null) {
                           headers = new Form();
                        }
                        headers.add(name,value);
                     } else if (e.getName().equals(MessageSequence.AUTHORIZATION)) {
                        String scheme = e.getAttributeValue("scheme");
                        String id = e.getAttributeValue("id");
                        String secret = e.getAttributeValue("secret");
                        challengeResponse = new ChallengeResponse(ChallengeScheme.valueOf(scheme),id,secret);
                     } else if (e.getName().equals(MessageSequence.COOKIE)) {
                        String name = e.getAttributeValue("name");
                        String value = e.getAttributeValue("value");
                        String domain = e.getAttributeValue("domain");
                        String path = e.getAttributeValue("path");
                        if (name!=null && value!=null) {
                           getLogger().info("Decoded cookie "+name);
                           if (cookies==null) {
                              cookies = new ArrayList<Cookie>();
                           }
                           Cookie cookie = new Cookie(name,value);
                           if (domain!=null) {
                              cookie.setDomain(domain);
                           }
                           if (path!=null) {
                              cookie.setPath(path);
                           }
                           cookies.add(cookie);
                        }
                     } else if (e.getName().equals(MessageSequence.BODY)) {
                        hasBody = true;
                        mediaType = e.getAttributeValue("type");
                        chunked = "true".equals(e.getAttributeValue("chunked"));
                        String v = e.getAttributeValue("size");
                        size = v==null ? -1 : Long.parseLong(v);
                        builder = new StringBuilder();
                        engine.push(new BuilderHandler(builder),item);
                     }
                  break;
               }
            }
         }
      }
   }
   static Map<UUID,ChunkedRepresentation> waitingRepresentations = new TreeMap<UUID,ChunkedRepresentation>();

   long connectionManagerWait = 10000;
   AtomicBoolean connectionManagerRunning;
   Thread connectionManager;
   XMPP.Identity identity;
   DocumentLoader loader;
   boolean running;
   
    /**
     * Constructor.
     * 
     * @param server
     *            The server to help.
     */
   public XMPPServerHelper(Server server) {
      super(server);
      getProtocols().add(XMPP.XMPP_PROTOCOL);
      loader = new SAXDocumentLoader();
      running = false;
   }
   
   public void setConnectionManagerWait(long wait)
   {
      connectionManagerWait = wait;
   }
   
   public long getConnectionManagerWait() {
      return connectionManagerWait;
   }
    
    /** Stops the Restlet. */
   public void stop() throws Exception {
      super.stop();
      getLogger().info("Stopping server "+identity.getId());
      connectionManagerRunning.set(false);
      getLogger().info("Disconnecting "+identity.getId());
      identity.disconnect();
      connectionManager.interrupt();
      connectionManager.join(2000);
      getLogger().info("Stopped server "+identity.getId());
      running = false;
   }
   
   void connect() 
      throws XMPPException
   {
      if (identity.wasConnected()) {
         identity.reconnect();
      } else if (!identity.isConnected()) {
         identity.connect();
      }
      getLogger().info("Connected as "+identity.getConnectionId());
      
      identity.connection.startReceiver(new DestinationSource() {
         public ItemDestination getDestination()
            throws XMLException 
         {
            return new HandlerEngine(new StanzaHandler());
         }
 
         public ItemDestination getDestination(URI baseURI)
            throws XMLException 
         {
            return getDestination();
         }
         public ItemConstructor createItemConstructor() {
            return InfosetFactory.getDefaultInfoset().createItemConstructor();
         }
         public Infoset getInfoset() {
            return InfosetFactory.getDefaultInfoset();
         }
      });
      //identity.connection.startKeepAlive();
    }

   /** Starts the Restlet. */
   public void start() throws Exception {
      super.start();
      getLogger().info("Logger name: "+getLogger().getName());
      XMPP xmpp = (XMPP)getHelped().getContext().getAttributes().get(XMPP.XMPP_ATTR);
      if (xmpp==null) {
         throw new java.lang.IllegalStateException("XMPP instance is null!");
      }
      String xmppid = getHelped().getAddress();
      if (xmppid==null) {
         throw new java.lang.IllegalStateException("Address is null!");
      }
      identity = xmpp.getIdentity(new ID(xmppid));
      if (identity==null) {
         throw new java.lang.IllegalStateException("Identity "+xmppid+" cannot be found.");
      }
      getLogger().info("Receiving over XMPP as "+xmppid);

      if (identity.isConnected()) {
         // start the receiver (won't reconnect)
         connect();
      }

      connectionManagerRunning = new AtomicBoolean(true);
      connectionManager = new Thread(threadGroup,new Runnable() {
         int count = 0;
         public void run() {
            while (connectionManagerRunning.get()) {
               if (!identity.isConnected()) {
                  try {
                     if (identity.wasConnected()) {
                        identity.notifyOffline(identity.getConnectionId());
                        getLogger().info("Identity "+identity.getConnectionId()+" has been disconnected, reconnecting...");
                     }
                     connect();
                  } catch (XMPPException ex) {
                     getLogger().log(Level.SEVERE,"Cannot connect to identity.",ex);
                  }
               }
               count++;
               if (count%12==0) {
                  getLogger().info("Identity "+identity.getId()+" is "+(identity.isConnected() ? "online" : "offline"));
               }
               try {
                  Thread.currentThread().sleep(connectionManagerWait);
               } catch (InterruptedException ex) {
               }
            }
            getLogger().info("Connection manager exiting.");
         }
      },"connection-manager "+identity.getId());
      connectionManager.start();
      running = true;
    }
    /**
     * Returns the default number of polling threads for a handler object.
     * 
     * @return The default number of polling threads for a handler object.
     */
    public int getDefaultThreads() {
        return Integer.parseInt(getHelpedParameters().getFirstValue("defaultThreads",
                "20"));
    }

    /**
     * Returns the maximum waiting time between polls of the input.
     * 
     * @return The maximum waiting time between polls of the input.
     */
    public int getMaxWaitTimeMs() {
        return Integer.parseInt(getHelpedParameters().getFirstValue("maxWaitTimeMs",
                "200"));
    }


}
